package edu.hawaii.senin.iclouds.basics;

import java.io.BufferedWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.TreeMap;

/**
 * Defines reaction.
 * 
 * @author Pavel Senin
 * 
 */

public class Reaction {

  /**
   * Describes the molecule.
   */
  private String name;

  /**
   * Contains reactants used in the reaction.
   */
  private final ArrayList<String> reactants;

  /**
   * Contains products used in the reaction.
   */
  private final ArrayList<String> products;

  /**
   * Reaction rate.
   */
  private Double rate;

  /**
   * Reaction type.
   */
  private String type;

  /**
   * REaction energy barrier.
   */
  private Double eBarrier;

  /**
   * Probability of overcoming energy barrier.
   */
  private Double chi;

  private final DecimalFormat decFormatE = new DecimalFormat("0.00000E00");

  /**
   * Instantiates Molecule with given Reactants and Products List.
   * 
   * @param description molecule description.
   * @param rate reaction rate.
   * @param eBarrier reaction energy barrier.
   * @param type reaction type.
   * @param reactants ArrayList specifying reactants.
   * @param products ArrayList specifying products.
   * @throws ReadConfigException when unable to parse the config provided.
   */
  public Reaction(String description, Double eBarrier, Double rate, String type,
      ArrayList<String> reactants, ArrayList<String> products) throws ReadConfigException {
    this.name = description;
    this.rate = rate;
    this.eBarrier = eBarrier;
    this.type = type;

    this.reactants = reactants;
    this.products = products;

    // check this stuff
    TreeMap<String, IntegerAccumulator> rt = new TreeMap<String, IntegerAccumulator>();
    TreeMap<String, IntegerAccumulator> pr = new TreeMap<String, IntegerAccumulator>();

    // parse reactants
    for (String sp : this.reactants) {
      Integer curr_pos = 0;
      while (curr_pos < sp.length()) {
        // check for number
        if (Character.isLetter(sp.charAt(curr_pos))) {
          String spName = String.valueOf(sp.charAt(curr_pos));
          Integer num = 1;
          curr_pos += 1;
          if ((curr_pos < sp.length()) && (Character.isDigit(sp.charAt(curr_pos)))) {
            num = Integer.valueOf(String.valueOf(sp.charAt(curr_pos)));
            curr_pos += 1;
          }
          if (rt.containsKey(spName)) {
            rt.get(spName).increment(num);
          }
          else {
            rt.put(spName, new IntegerAccumulator(spName, num));
          }
        }
        else {
          throw (new ReadConfigException("The reaction " + this.name
              + " is bad formatted, check it now!"));
        }
      }
    }

    // parse products
    for (String sp : this.products) {
      Integer curr_pos = 0;
      while (curr_pos < sp.length()) {
        // check for number
        if (Character.isLetter(sp.charAt(curr_pos))) {
          String spName = String.valueOf(sp.charAt(curr_pos));
          Integer num = 1;
          curr_pos += 1;
          if ((curr_pos < sp.length()) && (Character.isDigit(sp.charAt(curr_pos)))) {
            num = Integer.valueOf(String.valueOf(sp.charAt(curr_pos)));
            curr_pos += 1;
          }
          if (pr.containsKey(spName)) {
            pr.get(spName).increment(num);
          }
          else {
            pr.put(spName, new IntegerAccumulator(spName, num));
          }
        }
        else {
          throw (new ReadConfigException("The reaction " + this.name
              + " is bad formatted, check it now!"));
        }
      }
    }

    // check-up
    for (IntegerAccumulator ac1 : rt.values()) {

      IntegerAccumulator ac2 = pr.get(ac1.getName());

      if (null == ac2) {
        throw (new ReadConfigException("The reaction " + this.name
            + " is not properly configured, the amount of " + ac1.getName() + " in reactants is "
            + ac1.getValue() + ", but it doesn't found in products at all"));
      }
      if (ac1.getValue().equals(ac2.getValue())) {
        assert true;
      }
      else {
        throw (new ReadConfigException("The reaction " + this.name
            + " is not properly configured, the amount of " + ac1.getName() + " in reactants is "
            + ac1.getValue() + ", but in product is " + ac2.getValue()));
      }
    }

  }

  /**
   * Sets the reaction rate.
   * 
   * @param rate rate to set.
   */
  public void setRate(Double rate) {
    this.rate = rate;
  }

  /**
   * Returns the current rate.
   * 
   * @return current rate.
   */
  public Double getRate() {
    return this.rate;
  }

  /**
   * Sets new description for molecule.
   * 
   * @param description the new description.
   */
  public void setName(String description) {
    this.name = description;
  }

  /**
   * Gets the description for molecule.
   * 
   * @return the molecule description.
   */
  public String getName() {
    return this.name;
  }

  /**
   * Sets the type for reaction.
   * 
   * @param type - new type to set.
   */
  public void setType(String type) {
    this.type = type;
  }

  /**
   * Gets the type for reaction.
   * 
   * @return the molecule description.
   */
  public String getType() {
    return this.type;
  }

  /**
   * Sets new energy barrier.
   * 
   * @param barrier new barrier to set.
   */
  public void setEBarrier(Double barrier) {
    this.eBarrier = barrier;
  }

  /**
   * Returns current eBarrier value.
   * 
   * @return current value.
   */
  public Double getEBarrier() {
    return this.eBarrier;
  }

  /**
   * Returns current Chi value.
   * 
   * @return current value.
   */
  public Double getChi() {
    return this.chi;
  }

  /**
   * Sets new energy barrier.
   * 
   * @param barrier new barrier to set.
   */
  public void setChi(Double barrier) {
    this.chi = barrier;
  }

  /**
   * Returns all reactants.
   * 
   * @return the Object[] array.
   */
  public ArrayList<String> getReactants() {
    return this.reactants;
  }

  /**
   * Returns all products.
   * 
   * @return the Object[] array.
   */
  public ArrayList<String> getProducts() {
    return this.products;
  }

  /**
   * Converts current Reaction instance into string. This method used by Logger.
   * 
   * @return text representation of this Reaction instance.
   */
  public String toText() {
    StringBuffer sb = new StringBuffer(500);
    sb.append(this.name + ": ");
    sb.append(", type: " + this.type);
    sb.append(", eBarrier: " + this.eBarrier + ", reactants: [");
    for (String s : this.reactants) {
      sb.append(s + ", ");
    }
    sb.deleteCharAt(sb.length() - 2);
    sb.append("], product(s): [");
    for (String s : this.products) {
      sb.append(s + ", ");
    }
    sb.deleteCharAt(sb.length() - 2);
    sb.append("]\n");
    return sb.toString();
  }

  /**
   * Dumps XML-formatted Reaction into provided writer stream.
   * 
   * @param out - stream to use.
   * 
   * @throws IOException when IO error encountered.
   */
  public void dumpToWriter(BufferedWriter out) throws IOException {
    out.write("<reaction>\n");
    out.write("<name>" + this.name + "</name>\n");
    out.write("<type>" + this.type + "</type>\n");
    out.write("<reactants>\n");
    for (String s : this.reactants) {
      if (1 == s.length()) {
        out.write("<atom>" + s + "</atom>\n");
      }
      else {
        out.write("<molecule>" + s + "</molecule>\n");
      }
    }
    out.write("</reactants>\n");
    out.write("<products>\n");
    for (String s : this.products) {
      if (1 == s.length()) {
        out.write("<atom>" + s + "</atom>\n");
      }
      else {
        out.write("<molecule>" + s + "</molecule>\n");
      }
    }
    out.write("</products>\n");
    out.write("<energy_barrier>" + this.eBarrier + "</energy_barrier>\n");
    out.write("</reaction>\n");

  }

  /**
   * Dumps reaction into array. Used into View Table component.
   * 
   * @return current reaction array representation.
   */
  public Object[] toArray() {
    Object[] res = new Object[6];

    res[0] = this.name;
    res[1] = this.type;
    res[2] = this.eBarrier;
    res[3] = decFormatE.format(this.chi);
    res[4] = this.reactants;
    res[5] = this.products;
    return res;
  }
}
